<?php

namespace App\Kuafu\Pay\Channel\AliPay;

use Alipay\EasySDK\Kernel\Config;
use Alipay\EasySDK\Kernel\Factory;
use Alipay\EasySDK\Kernel\Util\ResponseChecker;
use App\Kuafu\Pay\BasePay;
use App\Kuafu\Pay\Enum\AliPayEnum;
use App\Kuafu\Pay\Enum\BasePayEnum;
use App\Kuafu\Pay\Enum\FuYouPayEnum;
use App\Kuafu\Pay\Enum\PayCodeEnum;
use App\Kuafu\Pay\Exceptions\PayException;
use App\Kuafu\Pay\Service\PayConfigService;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;

class AliPay extends BasePay
{
    /**
     * 支付配置环境
     * @var string
     */
    private int $company;

    public function __construct(int $shopId, int $sysType, int $payChannel, int $forceCompany)
    {
        // easysdk，在php7.4存在问题，需要忽略
        // Invalid characters passed for attempted conversion, these have been ignored
        error_reporting(E_ALL ^ E_DEPRECATED);

        parent::__construct($shopId, $sysType, $payChannel, $forceCompany);
    }

    // 初始化配置信息
    protected function initConfig(int $shopId, int $sysType)
    {

        /*
                $options->protocol    = 'https';
                $options->gatewayHost = config('pay.ali.directAppPay.host');
                $options->signType    = 'RSA2';

                $options->appId = config('pay.ali.directAppPay.appId');

                // 为避免私钥随源码泄露，推荐从文件中读取私钥字符串而不是写入源码中
                $options->merchantPrivateKey = config('pay.ali.directAppPay.merchantPrivateKey');

                //        // 请填写您的支付宝公钥证书文件路径，例如：/foo/alipayCertPublicKey_RSA2.cr
//        $options->alipayCertPath = config('pay.ali.alipayCertPath');
//
//        // 请填写您的支付宝根证书文件路径，例如：/foo/alipayRootCert.crt
//        $options->alipayRootCertPath = config('pay.ali.alipayRootCertPath');
//
//        // 请填写您的应用公钥证书文件路径，例如：/foo/appCertPublicKey_2019051064521003.crt
//        $options->merchantCertPath = config('pay.ali.merchantCertPath');

                //注：如果采用非证书模式，则无需赋值上面的三个证书路径，改为赋值如下的支付宝公钥字符串即可
                $options->alipayPublicKey = config('pay.ali.directAppPay.alipayPublicKey');

                //可设置异步通知接收服务地址（可选）
//        $options->notifyUrl = config('pay.ali.directAppPay.notifyUrl');

                //可设置AES密钥，调用AES加解密相关接口时需要（可选）
//        $options->encryptKey = "<-- 请填写您的AES密钥，例如：aa4BtZ4tspm2wnXLb1ThQA== -->";
                $options->encryptKey = config('pay.ali.directAppPay.encryptKey');
         */
        $options                     = new Config();
        $options->protocol           = 'https';
        $options->gatewayHost        = $this->payConfig['host'];
        $options->signType           = 'RSA2';
        $options->appId              = $this->payConfig['appId'];
        $options->merchantPrivateKey = $this->payConfig['merchantPrivateKey'];
        $options->alipayPublicKey    = $this->payConfig['alipayPublicKey'];
        $options->encryptKey         = $this->payConfig['encryptKey'];
        $options->notifyUrl          = $this->payConfig['notifyUrl'];

        Factory::setOptions($options);

        $this->company = $this->payConfig['company'];
    }

    /**
     * 当面付 - 被扫支付
     * @param string $subject
     * @param int $amount
     * @param string $authCode
     * @return array
     * @throws PayException
     */
    public function faceToFacePay(string $subject, int $amount, string $authCode): array
    {
        $totalAmount = bcdiv($amount, 100, 2);

        // 我放流水，支持外部传入
        $outTradeNo = $this->getOutTradeNo();

        $result    = Factory::payment()
                            ->faceToFace()
                            ->pay($subject, $outTradeNo, $totalAmount, $authCode);
        $formatRes = $this->commonReturn($result);

        $successTime = 0;
        $thirdPayNo  = '';

        if ($formatRes['status']) {

            // 付款成功
            $tradeStatus = BasePayEnum::TRADE_STATUS_OK;

            // 付款成功时间
            $successTime = strtotime($formatRes['oriData']['gmt_payment'] ?? '');

            // 三方流水号
            $thirdPayNo = $formatRes['oriData']['trade_no'];

        } else {
            if (!$formatRes['status'] && $formatRes['code'] == '10003') {

                // 待用户输入密码
                $tradeStatus = BasePayEnum::TRADE_STATUS_WAIT;
            } else {

                // 失败
                $tradeStatus = BasePayEnum::TRADE_STATUS_CLOSED;
            }
        }

        $formatRes['data'] = [
            'payNo'          => $outTradeNo,
            'thirdPayNo'     => $thirdPayNo,
            'tradeStatus'    => $tradeStatus,
            'paySuccessTime' => $successTime,
        ];

        return $formatRes;
    }

    /**
     * 关闭订单
     * 支付交易返回失败或支付系统超时，调用该接口撤销交易。如果此订单用户支付失败，支付宝系统会将此订单关闭；如果用户支付成功，支付宝系统会将此订单资金退还给用户。
     * @param string $outTradeNo
     * @return array
     * @throws \Exception
     */
    public function closePay(string $outTradeNo): array
    {
        $result    = Factory::payment()
                            ->common()
                            ->cancel($outTradeNo);
        $formatRes = $this->commonReturn($result);

        // kf订单号
//        $formatRes['data']['outTradeNo'] = $outTradeNo;

        return $formatRes;
    }

    // 退款
    public function refund(string $outTradeNo, int $refundAmount)
    {
        $refundAmount = bcdiv($refundAmount, 100, 2);
        $outRequestNo = $this->getOutTradeNo();

        $result    = Factory::payment()
                            ->common()
                            ->optional('out_request_no', $outRequestNo)
                            ->refund($outTradeNo, $refundAmount);
        $formatRes = $this->commonReturn($result);
        if (!$formatRes['status']) {
            return $formatRes;
        }

        $fundChange = $formatRes['oriData']['fund_change'] ?? 'N';

        $formatRes['data'] = [
            'fundChange'    => $fundChange == 'Y' ? true : false, // 本次退款是否发生了资金变化
            'payNo'         => $outTradeNo, // 原支付流水号
            'refundNo'      => $outRequestNo, // 退款唯一流水号
            'thirdRefundNo' => $formatRes['oriData']['trade_no'] ?? '',
            'refundAmount'  => $refundAmount,
        ];

        return $formatRes;
    }

    // 查询订单
    public function query(string $outTradeNo)
    {
        $result    = Factory::payment()
                            ->common()
                            ->query($outTradeNo);
        $formatRes = $this->commonReturn($result);
        if (!$formatRes['status']) {
            return $formatRes;
        }

        // 转换支付状态
        $formatRes['data'] = [
            'commonTradeStatus' => $this->convertTradeStatus($formatRes['oriData']['trade_status'] ?? ''),
            'totalPrice'        => (int)bcmul($formatRes['oriData']['total_amount'] ?? 0, 100, 2),
            'payNo'             => $outTradeNo,
            'thirdPayNo'        => $formatRes['oriData']['trade_no'] ?? '',
            'paySuccessTime'    => strtotime($formatRes['oriData']['send_pay_date'] ?? ''),
        ];
        return $formatRes;
    }

    // 退款订单查询
    public function refundQuery(string $outTradeNo, string $outRequestNo)
    {
        $result = Factory::payment()
                         ->common()
                         ->optional('query_options', ['gmt_refund_pay'])
                         ->queryRefund($outTradeNo, $outRequestNo);

        $formatRes = $this->commonReturn($result);
        if (!$formatRes['status']) {
            return $formatRes;
        }

        $apiRefundStatus = $formatRes['oriData']['refund_status'] ?? '';

        // 沙箱默认退款成功
        if ($this->company == PayConfigService::COMPANY_ALI_SAND) {
            $apiRefundStatus = AliPayEnum::REFUND_STATUS_OK;
        }

        $formatRes['data'] = [
            'payNo'             => $formatRes['oriData']['out_trade_no'] ?? '',
            'refundNo'          => $formatRes['oriData']['out_request_no'] ?? '',
            'thirdRefundNo'     => $formatRes['oriData']['trade_no'] ?? '',
            'totalAmount'       => bcmul($formatRes['oriData']['totalAmount'] ?? 0, 100, 2),
            'refundStatus'      => $this->convertRefundStatus($apiRefundStatus),
            'refundSuccessTime' => strtotime($formatRes['oriData']['gmt_refund_pay'] ?? ''),
        ];

        return $formatRes;
    }

    /**
     * app支付
     * @param string $subject
     * @param int $amount
     * @return array
     */
    public function appPay(string $subject, int $amount, int $expireTime = 0)
    {
        $outTradeNo  = $this->getOutTradeNo();
        $totalAmount = bcdiv($amount, 100, 2);


        $obj = Factory::payment()
                      ->app()
                      ->asyncNotify(config('pay.ali.notifyUrl'));

        if (isTest() && $amount == 1 && $expireTime == 0) {
            $expireTime = time() + 20;
        }

        // 超时时间
        if ($expireTime) {
            $expireAt = date('Y-m-d H:i:s', $expireTime);
            $obj->optional('time_expire', $expireAt);
        }

        $result            = $obj->pay($subject, $outTradeNo, $totalAmount);
        $formatRes         = $this->commonReturn($result);
        $formatRes['data'] = [
            'appPayData' => $formatRes['oriData'], // 唤起app支付的数据
            'payNo'      => $outTradeNo,
        ];

        return $formatRes;
    }

    /**
     * H5支付
     * @param string $subject
     * @param int $amount
     * @param string $quitUrl
     * @param string $returnUrl
     * @return array
     */
    public function wapPay(string $subject, int $amount, string $quitUrl, string $returnUrl)
    {
        $outTradeNo  = $this->getOutTradeNo();
        $totalAmount = bcdiv($amount, 100, 2);

        $result = Factory::payment()
                         ->wap()
                         ->pay($subject, $outTradeNo, $totalAmount, $quitUrl, $returnUrl);

        $formatRes         = $this->commonReturn($result);
        $formatRes['data'] = [
            'wapPayData' => $formatRes['oriData'], // 唤起app支付的数据
            'payNo'      => $outTradeNo,
        ];

        return $formatRes;
    }

    /**
     * 对账单
     * $date
     * @param string $date 支持Y-m-d 、Y-m；最早可下载前一日的数据
     * @return array|mixed
     * @throws PayException
     */
    public function downloadBill(string $date)
    {
        $result    = Factory::payment()
                            ->common()
                            ->downloadBill('trade', $date);

        $formatRes = $this->commonReturn($result);
        if (!$formatRes['status']) {
            return $formatRes;
        }

        $formatRes['data'] = [
            'url' => $formatRes['oriData']['bill_download_url'] ?? '',
        ];

        return $formatRes;
    }

    /**
     * 解密app支付回调数据
     * @param array $body
     * @return array
     */
    public function decryptAppCallback(array $body)
    {
        // 支付宝沙箱环境，目前查询走不通，直接返回成功
        if (isset($body['seller_email']) && $body['seller_email'] == 'vkhtku8690@sandbox.com') {
            $formatRes = self::returnFormatData(true, '', '', []);

            $formatRes['data'] = [
                'commonTradeStatus' => BasePayEnum::TRADE_STATUS_OK,
                'totalPrice'        => (int)bcmul($body['total_amount'] ?? 0, 100, 2),
                'payNo'             => $body['out_trade_no'],
                'thirdPayNo'        => $body['out_trade_no'],
                'paySuccessTime'    => strtotime($body['gmt_payment']),
            ];

            return $formatRes;
        }

        return $this->query($body['out_trade_no']);
    }

    protected function commonReturn($result): array
    {
        $res = $result->toMap() ?? [];
        Log::info('AliPay Api Response:', $res);

        $responseChecker = new ResponseChecker();
        if ($responseChecker->success($result)) {
            $code   = $res['code'] ?? '';
            $msg    = $res['msg'] ?? '';
            $status = true;

        } else {
            $code = $res['code'] ?? '';
            $msg  = $res['msg'] ?? '';

            if (isset($res['sub_code'])) {
                $code = $res['sub_code'];
            }

            if (isset($res['sub_msg'])) {
                $msg = $res['sub_msg'];
            }

            $status = false;
        }

        return self::returnFormatData($status, $code, $msg, $res);
    }

    /**
     * 转换支付状态
     * @param string $apiTradeStatus
     * @return int
     */
    protected function convertTradeStatus(string $apiTradeStatus): int
    {
        return [
                AliPayEnum::TRADE_STATUS_WAIT   => BasePayEnum::TRADE_STATUS_WAIT,
                AliPayEnum::TRADE_STATUS_CLOSED => BasePayEnum::TRADE_STATUS_CLOSED,
                AliPayEnum::TRADE_STATUS_OK     => BasePayEnum::TRADE_STATUS_OK,
                AliPayEnum::TRADE_STATUS_END    => BasePayEnum::TRADE_STATUS_CLOSED,
            ][$apiTradeStatus] ?? BasePayEnum::TRADE_STATUS_WAIT;
    }

    /**
     * 转换退款状态
     * @param string $apiRefundStatus
     * @return int
     */
    protected function convertRefundStatus(string $apiRefundStatus): int
    {
        return [
                AliPayEnum::REFUND_STATUS_OK   => BasePayEnum::REFUND_STATUS_OK,
                AliPayEnum::REFUND_STATUS_ELSE => BasePayEnum::REFUND_STATUS_ERROR,
            ][$apiRefundStatus] ?? BasePayEnum::REFUND_STATUS_WAIT;
    }

    /**
     * 获取手续费
     * @param int $payPrice
     * @return float
     */
    public function getFeeAmtByPayPrice(int $payPrice): array
    {
        return [
            round($payPrice * AliPayEnum::PAYMENT_RATE), // fee
            number_format(AliPayEnum::PAYMENT_RATE, 6, '.', ''), // feeRate
        ];
    }
}
